package com.nf.mvc.cors;

import com.nf.mvc.support.HttpHeaders;
import com.nf.mvc.support.HttpMethod;
import com.nf.mvc.util.CorsUtils;
import com.nf.mvc.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class DefaultCorsProcessor implements CorsProcessor {
    @Override
    public boolean processCors(HttpServletRequest req, HttpServletResponse resp, CorsConfiguration config) throws IOException {

        // 如果不是跨域请求，直接返回true，提前结束方法，
        // processCors方法的调用者需要继续进行后续的请求处理
        if (!CorsUtils.isCorsRequest(req)) {
            return true;
        }

        return handleInternal(req, resp, config);
    }

    /**
     * 参考spring 的DefaultCorsProcessor
     * <p>在预检请求下，才会设置的项
     * <ul>
     *     <li>setAccessControlMaxAge</li>
     *     <li>setAccessControlAllowHeaders</li>
     *     <li>setAccessControlAllowMethods</li>
     * </ul>
     * </p>
     *
     * <p>不管是不是预检请求都会设置的项有
     *     <ul>
     *         <li>setAccessControlAllowOrigin</li>
     *         <li>setAccessControlAllowCredentials</li>
     *     </ul>
     * </p>
     *
     * @param req           请求对象
     * @param resp          响应对象
     * @param configuration 跨域配置
     */
    protected boolean handleInternal(HttpServletRequest req, HttpServletResponse resp, CorsConfiguration configuration) throws IOException {
        String requestOrigin = req.getHeader(HttpHeaders.ORIGIN);
        String allowOrigin = configuration.checkOrigin(requestOrigin);
        if (allowOrigin == null) {
            rejectRequest(resp);
            return false;
        }
        // 设置允许跨域请求的源
        resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin);
        resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(configuration.getAllowCredentials()));

        if (HttpMethod.OPTIONS.matches(req.getMethod())) {
            // 浏览器缓存预检请求结果时间,单位:秒
            resp.setHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, Long.toString(configuration.getMaxAge()));
            // 允许浏览器在预检请求成功之后发送的实际请求方法名，
            // 在MDN中只说要用逗号分隔即可，https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
            // 但其举的例子是逗号后有一个空格，spring的HttpHeaders类的toCommaDelimitedString也是这样的
            resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, StringUtils.toCommaDelimitedString(configuration.getAllowedMethods(), ", "));
            // 允许浏览器发送的请求消息头
            resp.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, StringUtils.toCommaDelimitedString(configuration.getAllowedHeaders(), ", "));
        }
        resp.flushBuffer();
        //是预检请求的话就不需要进行后续的处理，否则就还需要继续走后面的请求处理流程,也就是进到DispatcherServlet的doService方法里
        return !CorsUtils.isPreFlightRequest(req);
    }

    protected void rejectRequest(HttpServletResponse response) throws IOException {
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        response.getOutputStream().write("无效的跨域请求".getBytes(StandardCharsets.UTF_8));
        response.flushBuffer();
    }
}
